#Import Modules
import os, pygame, math
from pygame.locals import *
from Spawner import Spawner

class PhysicsObject(pygame.sprite.Sprite):
    """A class required by all sprites that expect to move or run through the physics engine."""
    wall = None
    ground = None
    unified = None

    def __init__(self,spawnpoint):
        pygame.sprite.Sprite.__init__(self)
        # This class init contains the default values for all physics objects, but anything can be changed by the game.
        # For example, an airbourne enemy might set its gravity to zero to simplify movement routines.
        
        self.name = ""
        
        # Object acceleration
        self.accelerationx = 0.0    # Current forces
        self.accelerationy = 0.0    # Current forces
        
        # Object speed
        self.speedx = 0.0       # Current speed
        self.speedy = 0.0       # Current speed
        
        # Additional physics properties for walking on moving platforms
        self.groundx = 0.0
        self.groundy = 0.0
        
        # Object Position
        self.spawnpoint = spawnpoint                    # Object's starting position
        self.positionx, self.positiony = spawnpoint     # Current virtual position (THIS IS NOT SCREEN POSITION)
                                                        # (Screen position is calculated by the camera)
        self.lastpositionx, self.lastpositiony = spawnpoint     # Last virtual position (THIS IS NOT SCREEN POSITION)
                                                                # (Screen position is calculated by the camera)
        
        # Physics constants
        self.mass = 1
        self.friction = 0.4     # Affects skidding on the ground as well as jumping through the air
        self.gravity = 0.8      # Storage of perm. gravity value
        self.elasticity = 0.6   # The proportion of force you retain after hitting a wall or ceiling
                                # Note: This applies only on the X axis when the player hits ground.
                                #       as the ground will absorb all of the Y axis velocity
                                
        # Object Padding
        self.padx = 0
        self.pady = 0
        
        # Attackboxes are tuples containing a pygame Rect (relative to the object's position) and an attack strength.
        self.attackboxes = list()
        
        # Basic Object States
        self.dying = 0
        self.dead = 0
        
        # Basic Object Vars
        self.maxhealth = 1
        self.health = 1
        self.invincible = 0
        self.invincibleframes = 0
        self.stun = 0
        self.spawner = self.spawner = Spawner(self)
        self.screeneffect = 0
        
        # Attack engine vars
        self.type = None
        self.attackgroups = list()
        
    def respawn(self):
        # Respawn
        self.dying = 0
        self.dead = 0
        self.positionx, self.positiony = self.spawnpoint
        self.lastpositionx, self.lastpositiony = self.spawnpoint
        self.health = self.maxhealth
        self.speedx = 0.0
        self.speedy = 0.0
    
    def hit(self, damage, stun=20, direction=-1, knockback=10):
        if knockback > 10:
            knockback = 10
        if self.invincible == 0:
            self.health -= damage
            self.invincible = self.invincibleframes
            self.stun = stun
            return True
        return False
    
    def update(self):
        # When any PhysicsObject runs out of health, it dies
        if self.invincible > 0:
            self.invincible -= 1
        if self.stun > 0:
            self.stun -= 1
        if self.health <= 0:
            self.die()
        
    def die(self):
        # When any PhysicsObject is told to die, it will start dying. 
        # Extended classes are in charge of actually setting the dead flag.
        if self.dying == 0:
            self.dying = 1
            return True
        return False
    
    # A basic physics object does it's calculus
    # Acceleration -> Velocity -> Position
    def physics(self):
        # Add self gravity accel to speed
        self.speedx = self.speedx + self.accelerationx
        self.speedy = self.speedy + self.accelerationy
        
        self.positionx = self.positionx + self.speedx
        self.positiony = self.positiony + self.speedy
        
        # Convert speed to polar
        totalforce = math.pow(math.pow(self.speedx,2)+math.pow(self.speedy,2),0.5)
        angle = math.atan2(-self.speedy,self.speedx)
        
        # Apply friction to total speed.
        if totalforce > 20:
            totalforce = 20
        elif totalforce < self.friction:
            totalforce = 0
        else:
            totalforce = totalforce - self.friction
        
        # Convert back from polar, we don't need it anymore
        self.speedy = totalforce * -math.sin(angle)
        self.speedx = totalforce * math.cos(angle)
    
    @staticmethod
    def setup(w,g,u):
        PhysicsObject.wall = w
        PhysicsObject.ground = g
        PhysicsObject.unified = u
        
def collision(character, env):
    rect = character.rect.inflate(character.padx,character.pady)
    rect.x = character.positionx - (character.padx/2)
    rect.y = character.positiony - (character.pady/2)
    envrect = Rect((0,0),(env.rect.width,env.rect.height))
    check = envrect.clip(rect)
    return env.a[check.left:check.left+check.width,check.top:check.top+check.height].any()

def collisionang(character, env):
    # Ejects character and returns the surface normal of the colision.
    rect = character.rect.inflate(character.padx,character.pady)
    rect.x = character.positionx - (character.padx/2)
    rect.y = character.positiony - (character.pady/2)
    envrect = Rect((0,0),(env.rect.width,env.rect.height))
    check = envrect.clip(rect)
    
    collpixels = env.a[check.left:check.left+check.width,check.top:check.top+check.height].nonzero()
    if len(collpixels[0]) == 0:
        return 5
    avgx = collpixels[0].mean()
    avgy = collpixels[1].mean()
    
    #midx = (collpixels[0].max()+collpixels[0].min())/2
    #midy = (collpixels[1].max()+collpixels[1].min())/2
    hitangle = math.atan2(-(avgy - rect.height/2),(avgx - rect.width/2 ))
    #hitangle = math.atan2(-(avgy - midy),(avgx - midx))

    hitangle = hitangle + (math.pi)
    
    # Make sure the hitangle is on a 180 to -180 scale
    if hitangle > math.pi:
        hitangle = hitangle - (2*math.pi) 
    
    return hitangle
    
def objcollisionang(character, env):
    # Ejects character and returns the surface normal of the colision.
    rect = character.rect.inflate(character.padx,character.pady)
    rect.x = character.positionx - (character.padx/2)
    rect.y = character.positiony - (character.pady/2)
    envrect = Rect((0,0),(env.rect.width,env.rect.height))
    check = envrect.clip(rect)
    
    collpixels = env.a[check.left:check.left+check.width,check.top:check.top+check.height].nonzero()
    if len(collpixels[0]) == 0:
        return 5
    avgx = collpixels[0].mean()
    avgy = collpixels[1].mean()
    
    midx = (collpixels[0].max()+collpixels[0].min())/2
    midy = (collpixels[1].max()+collpixels[1].min())/2
    
    hitangle = math.atan2(-(avgy - rect.height/2),(avgx - rect.width/2 ))
    #hitangle = math.atan2(-(avgy - midy),(avgx - midx))

    hitangle = hitangle + (math.pi)
    
    hitdeg = degrees(hitangle)
    
    # Make sure the hitangle is on a 180 to -180 scale
    if hitangle > math.pi:
        hitangle = hitangle - (2*math.pi) 
    
    return hitangle
    
def seekground(character, env):
# Finds collision mask of the ground (env), if any, 10 vertical pixels in either direction from the bottom of the sprite.
# Places obj1 on top of the ground if possible, sets virtual coordinates.
# Returns true if ground is found and followed. Otherwise returns false and leaves obj1 unmoved.
    rect = character.rect.inflate(character.padx,character.pady)
    rect.x = character.positionx - (character.padx/2)
    rect.y = character.positiony - (character.pady/2)
    bottom = rect.bottom
    rect.height = 20
    rect.centery = bottom
    envrect = Rect((0,0),(env.rect.width,env.rect.height))
    check = envrect.clip(rect)
    
    if check == 0:
        return False, 0
    
    collpixels = env.a[check.left:check.right,check.top:check.bottom].nonzero()
    
    if len(collpixels[0]) == 0:
        return False, 0
    topy = collpixels[1].min()
    character.positiony = character.positiony + (topy-10)
    return True, (topy-10)